/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Channel.h"
#include "Hardware.h"
#include <logs/Log.h>
#include <math/utl.h>
#include <AutoPtr.h>
#include <noise/Manager.h>
#include <noise/hard/Hardware.h>
#include "JavaMethodCacheSound.h"

#include <jni.h>


namespace liba
{
namespace noise
{
namespace hard
{
namespace android
{

static double calculate_attenuation(double distance, double min_distance, double max_distance)
{
	distance = math::clamp( distance, min_distance, max_distance );
	return pow( 2.0, -distance/min_distance);
}

static const int frequency_2d = 22050;
static const int frequency_3d = 22050;
static const int frequency_surround = 22050;
static const double minimum_update_part = 0.25;

ChannelStaticRep::ChannelStaticRep(const ChannelStaticRepName & name):
	soundId( 0 )
{
	AutoPtr<DataSource> source = Manager::create_datasource( name.name );
	if( !source )
		THROW(GluckBadResource( __FUNCTION__, "Could not create DataSource for static channel", name.name ));

	frequency = source->get_frequency();
	size = source->get_length();
	
	check_method_cache_sound();
	
	//create sound here
	jstring nameJStr = gJavaEnv->NewStringUTF(name.name.c_str());
	if (nameJStr != NULL) {
		soundId = gJavaEnv->CallStaticIntMethod(method_cache_sound->main_class, method_cache_sound->registerSound, nameJStr);
	}
}

ChannelStaticRep::~ChannelStaticRep()
{
	//release sound here
	gJavaEnv->CallStaticVoidMethod(method_cache_sound->main_class, method_cache_sound->unRegisterSound, soundId);
}

Channel::Channel():
	exact_volume( 1 ),
	exact_pan( 0 ),
	min_distance( 1 ),
	max_distance( 1E6 ),
	loop( true )
{}

Channel::~Channel()
{}

bool Channel::change_attribute(const Atom & name, const math::Vector3<float> & value)
{
	if( name == position_name )
	{
		exact_position = value;
		return true;
	}
	if( name == speed_name )
	{
		exact_speed = value;
		return true;
	}
	return hard::Channel::change_attribute(name, value);
}

bool Channel::change_attribute(const Atom & name, double value)
{
	if( name == volume_name )
	{
		exact_volume = math::clamp<double>( value, 0, 1);
		return true;
	}
	if( name == loop_name )
	{
		loop = (value != 0);
		return true;
	}
	if( name == pan_name )
	{
		exact_pan = value;
		return true;
	}
	if( name == minimum_distance_name )
	{
		min_distance = value;
		return true;
	}
	if( name == maximum_distance_name )
	{
		max_distance = value;
		return true;
	}
	return hard::Channel::change_attribute(name, value);
}

bool Channel::get_attribute(const Atom & name, math::Vector3<float> * value)const
{
	return hard::Channel::get_attribute(name, value);
}

bool Channel::get_attribute(const Atom & name, double * value)const
{
	if( name == volume_name )
	{
		*value = exact_volume;
		return true;
	}
	return hard::Channel::get_attribute(name, value);
}

ChannelStatic::ChannelStatic(Hardware * ha, const Atom & channel_type, const filesystem::String & name):
	finished( true ),
	approx_vol( 0 ),
	position( 0 ),
	channel_type( channel_type ),
	in_existance_flag(false),
	playingSoundId(0)
{
	load_rep(ChannelStaticRepName(ha, channel_type, name));
	ha->add_static_channel(this);
}

ChannelStatic::~ChannelStatic()
{
	if( !get_rep() )
		return;

	stop();
	
	if( in_existance() )
		begone();

	in_existance_flag = false;
	get_rep_name().hardware->remove_static_channel(this);
}


void ChannelStatic::life_cycle(double delta_time, const math::Vector3<float> & listener_position)
{
	if( !get_rep() || finished )
		return;
		
	approx_vol = calculate_attenuation( exact_position.length(listener_position), min_distance, max_distance );
	
	if(!finished )
	{
		position += int(get_rep()->get_frequency() * delta_time);
		if( position > (unsigned)get_rep()->get_size() )
			if( loop )
				position %= get_rep()->get_size();
			else
				finished = true;
	}

	if( in_existance_flag )
	{
		//update volume for 3d sounds
		if( get_rep_name().channel_type == hard::Hardware::channel_type_3d )
			gJavaEnv->CallStaticVoidMethod(method_cache_sound->main_class, method_cache_sound->setVolume, playingSoundId, approx_vol);
		else
			gJavaEnv->CallStaticVoidMethod(method_cache_sound->main_class, method_cache_sound->setVolume, playingSoundId, exact_volume);
	}
}

void ChannelStatic::come_to_existance()
{
	if( !get_rep() )
		return;

	position = 0;

	//set sound loop here (loop)
	//play sound
	playingSoundId = gJavaEnv->CallStaticIntMethod(method_cache_sound->main_class, method_cache_sound->playSound, get_rep()->soundId, loop);
	
	if(!playingSoundId)
	{
		return;
	}
	
	//set sound volume here
	if( get_rep_name().channel_type == hard::Hardware::channel_type_3d )
		gJavaEnv->CallStaticVoidMethod(method_cache_sound->main_class, method_cache_sound->setVolume, playingSoundId, approx_vol);
	else
		gJavaEnv->CallStaticVoidMethod(method_cache_sound->main_class, method_cache_sound->setVolume, playingSoundId, exact_volume);

	in_existance_flag = true;
}

void ChannelStatic::begone()
{
	if(!in_existance_flag)
		return;

	in_existance_flag = false;
	
	if( !get_rep() )
		return;

	//stop channel
	if(playingSoundId) {
		gJavaEnv->CallStaticVoidMethod(method_cache_sound->main_class, method_cache_sound->stopSound, playingSoundId);
	}

	playingSoundId = 0;
}

void ChannelStatic::play(DataSource * source)
{
	if( !get_rep() )
		return;
		
	stop();
	position = 0;
	finished = false;

	if( get_rep_name().channel_type == hard::Hardware::channel_type_3d )
	{
		//play the channel but do not activate it yet, hardware will activate it based on its volume
		get_rep_name().hardware->play_static_3d_channel(this);
	}
	else
	{
		come_to_existance();
	}
}

void ChannelStatic::stop()
{
	if( !get_rep() )
		return;
		
	if( get_rep_name().channel_type == hard::Hardware::channel_type_3d )
	{
		//notify hardware that channel have stopped
		get_rep_name().hardware->stop_static_3d_channel(this);
	}

	if( in_existance() )
		begone();

	finished = true;
	position = 0;
}

bool ChannelStatic::is_finished()
{
	if( !get_rep() )
		return true;
		
	if( get_rep_name().channel_type != hard::Hardware::channel_type_3d )
		life_cycle(0, math::Vector3<float>() );

	return finished;
}

void ChannelStatic::on_hardware_delete()
{
	stop();
	unload_rep();
}

} // namespace android
} // namespace hard
} // namespace noise
} // namesapce liba

